#include "MineChooser.h"
#include <cstdio>

MineChooser::MineChooser(void):x(10),y(10),PERTO(50),RAIO_MINERACAO(1000)
{
	Ponto tmp[] = {mp(x,0), mp(x,-y), mp(0, -y), mp(-x,-y), mp(-x,0), mp(-x,y), mp(0,y), mp(x,y)};
	for(int i = 0; i < 8; ++i) dx[i] = tmp[i];
	DistanciasCalculadas = false;
}

MineChooser::~MineChooser(void)
{
}


Position operator+(const BWAPI::Position& a, const Ponto& b){
	BWAPI::Position nova(a.x() + b.X, a.y() + b.Y);
	
	return nova;
}

bool MineComparator(const pair<Unidade*,Unidade*>& a, const pair<Unidade*,Unidade*>& b){
	Position Base = a.second->getPosition();

	return a.first->getPosition().getDistance(Base) < b.first->getPosition().getDistance(Base);
}

void MineChooser::Init(Unidade* base){
	if(!DistanciasCalculadas){
		set<Unidade*> minas = base -> getMinerals();
		while(!minas.size())minas = base->getMinerals();
		vector<pair<Unidade*,Unidade*> > MinasOrdenadas;
		for(SETI it = minas.begin(); it != minas.end(); ++it){
			MinasOrdenadas.push_back(mp(*it, base));
			DistanciaParaMinas[*it] = 1e50;
		}
		sort(MinasOrdenadas.begin(), MinasOrdenadas.end(),MineComparator);

		for(int i = 0; i < min(15, MinasOrdenadas.size()); ++i){
			double dist = Bfs(MinasOrdenadas[i].first->getPosition(), base -> getPosition());
			DistanciaParaMinas[MinasOrdenadas[i].first] = dist;
		}
		
		DistanciasCalculadas = true;
	}


}


double MineChooser::Bfs(BWAPI::Position a, BWAPI::Position b){
	while(!fila.empty()) fila.pop();
	mark.clear();

	fila.push(No(a,b,0));
	int cont = 0;
	while(!fila.empty()){
		BWAPI::Position at = fila.top().Pos;
		double dist = fila.top().Dist;
		
		if(at.getDistance(b) < PERTO){
			return dist + at.getDistance(b);
		}
		

		fila.pop();
		vector<Par> tmp;
		for(int i = 0; i < 8; ++i){
			BWAPI::Position next = at + dx[i];
			if(at.hasPath(next) && mark.insert(mp(next.x(), next.y())).second ){
				fila.push(No(next, b, dist + at.getDistance(next)));
				++cont;
			}
		}
	}

	return 1e50;
}

int MineChooser::CountUnitysWithType(set<Unidade*> Set, TYPE T){
	int Resp = 0;
	for(SETI it = Set.begin(); it != Set.end(); ++it){
		if((*it)->exists() && (*it)->getType() == T) ++Resp;
	}

	return Resp;
}

void MineChooser::AssignMine(Unidade* u){
	if(!DistanciasCalculadas) return;

	if(minaDoTrabalhador.find(u) != minaDoTrabalhador.end()){
		Unidade* tmp = minaDoTrabalhador[u];

		if(tmp != NULL && tmp -> exists()){
			u -> rightClick(tmp);
			return;
		}
		
		minasUsadas[tmp].erase(u);
	}

	double nota = 10000000000.0;
	Unidade* melhorMina = NULL;
	set<Unidade*> minas = u -> getMinerals();
	
	for(SETI it = minas.begin(); it != minas.end(); ++it){
		if(!(*it)->exists())continue;
		double utilidade = UtilityFunction(*it, u, CountUnitysWithType(minasUsadas[*it], PROBE));
		if(utilidade < nota){
			melhorMina = *it;
			nota = utilidade;
		}
	}

	if(melhorMina != NULL){
		u -> rightClick(melhorMina);
		minasUsadas[melhorMina].insert(u);
		minaDoTrabalhador[u] = melhorMina;
	}
}


double MineChooser::UtilityFunction(Unidade* mina, Unidade* trabalhador, int cont){
	double dist = DistanciaParaMinas[mina];
	return cont + (3*dist) / RAIO_MINERACAO;
}

